<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <style>
      .container {
        width: 600px;
        height: 400px;
        border: 1px solid #ccc;
        margin: 0 auto;
        position: relative;
        overflow: hidden;
      }
      .scroll {
        white-space: nowrap;
        transition: all 3s;
      }
      .input {
        position: absolute;
        top: 450px;
        left: 700px;
      }
      .anima {
        animation: fade 2s ease-in-out linear;
      }
    </style>
    <div class="container"></div>
    <div class="input">
      <button id="stop_run">暂停</button>
      <button id="start_run">开始</button>
    </div>
    <script>
      let container = document.querySelector(".container");
      let text = document.getElementById("text");
      let stop_run = document.getElementById("stop_run");
      let start_run = document.getElementById("start_run");
      let { width: containerWidth, right: containerRight } =
        container.getBoundingClientRect();
      let bullet = {
        left: 0,
        duration: 0,
        startTime: 0,
        top: 0,
        width: 0,
        height: 0,
      };
      let cnStr = "这是一个可以停止的弹幕";
      let abc = "abcdefghigklmnpqrst";
      let upperABC = abc.toUpperCase();
      let channel = [];
      let id = 0;
      let flag = true;
      let enterId;
      let zIndex = 9999;
      function randomNum(max, min) {
        return parseInt(Math.random() * (max - min + 1) + min, 10);
      }
      function productData() {
        let arr = [];
        let preWidth = 600;
        for (let i = 0; i < 50; i++) {
          let str =
            i +
            cnStr.substr(0, randomNum(-1, cnStr.length)) +
            upperABC.substr(0, randomNum(-1, upperABC.length)) +
            abc.substr(0, randomNum(-1, abc.length));
          arr.push(str);
        }
        return arr;
      }
      function enterBullet(e) {
        console.log("enter");
        if (flag) {
          flag = false;
          let index = this.channel.findIndex((val) => val._id === e.target._id);
          if (index !== -1) {
            let targetBullet = this.channel.splice(index, 1)[0];
            enterId = targetBullet._id;
            targetBullet.style.transform = `translateX(-${targetBullet.currentPosition}px)`;
            targetBullet.stopStartTime = new Date().getTime();
            targetBullet.distance = targetBullet.width + containerWidth;
          }
        }
      }
      function leaveBullet(e) {
        let targetBullet = e.target;
        if (enterId === targetBullet._id) {
          flag = true;
          targetBullet.style.transform = `translateX(-${targetBullet.distance}px)`;
          targetBullet.stopEndTime = new Date().getTime();
          targetBullet.stopTime +=
            targetBullet.stopEndTime - targetBullet.stopStartTime;
          targetBullet.style.transition = `all ${targetBullet.leftDruation}s linear 0s`;
          this.channel.unshift(targetBullet);
          if (this.channel.length === 1) {
            this.timer = window.requestAnimationFrame(
              this.loopWatcherLaunch.bind(this)
            );
          }
        }
      }
      function initBullet(str, top, type = "") {
        if (str) {
          let div = document.createElement("div");
          div.innerHTML = str;
          div.style.position = "absolute";
          div.style.left = containerWidth + "px";
          div.style.top = top + "px";
          div.style.whiteSpace = "nowrap";
          if (type === "current") {
            div.style.border = "1px solid red";
            div.className = "anima";
          }
          div.onmouseenter = enterBullet.bind(this);
          div.onmouseleave = leaveBullet.bind(this);
          container.appendChild(div);
          div.width = div.clientWidth;
          return div;
        }
        return null;
      }
      class Barrage {
        constructor({ container, data, top = 0, duration = 5 }) {
          this.data = data;
          this.channel = [];
          this.top = top;
          this.duration = duration;
          this.timer = null;
          this.init();
        }
        init() {
          this.loopWatcherLaunch = this.wrapper.call(this);
          this.loopWatcherLaunch.call(this);
        }
        wrapper() {
          let div = initBullet.call(this, this.data.shift(), this.top);
          return function () {
            let firstChnnel = this.channel[0];
            if (firstChnnel) {
              let timeNow = new Date().getTime();
              if (timeNow > firstChnnel.endTime + firstChnnel.stopTime) {
                container.removeChild(firstChnnel);
                this.channel.shift();
              }
            }
            // 子弹初始化
            let check = this.checkIsLauncch(div);
            if (check && div) {
              this.launchBullet.call(this, div);
              div = null;
              if (data.length > 0) {
                div = initBullet.call(this, this.data.shift(), this.top);
              }
            }
            // 实时更新子弹状态
            this.update();
            if (this.channel.length > 0) {
              this.timer = window.requestAnimationFrame(
                this.loopWatcherLaunch.bind(this)
              );
            }
          };
        }
        launchBullet(div) {
          let startTime = +new Date();
          let { width } = div;
          div.leftDistance = width + containerWidth;
          div.distance = width + containerWidth;
          div.startTime = startTime;
          div.currentTime = startTime;
          div.stopTime = 0;
          div.stopStartTime = startTime;
          div.stopEndTime = startTime;
          div.predictEndTime = startTime + this.duration * 1000;
          div.endTime = startTime + this.duration * 1000;
          div.duration = this.duration;
          div.moveV = div.distance / div.duration;
          div.leftDruation = div.leftDistance / div.moveV;
          div.currentPosition = div.distance - div.leftDistance;
          div._id = id++;
          if (zIndex < 1) zIndex = 9999;
          div.style.zIndex = zIndex--;
          div.style.transition = `all ${div.leftDruation}s linear 0s`;
          div.style.transform = `translateX(-${div.distance}px)`;

          this.channel.push(div);
        }
        update() {
          this.channel.map((val) => {
            val.currentTime = new Date().getTime();
            val.leftDistance =
              val.distance -
              ((val.currentTime - val.startTime - val.stopTime) / 1000) *
                val.moveV;
            val.leftDruation = val.leftDistance / val.moveV;
            val.currentPosition = val.distance - val.leftDistance;
          });
        }
        checkIsLauncch(dom) {
          let lastDom = this.channel[this.channel.length - 1];
          if (lastDom && dom) {
            let lastRect = lastDom.getBoundingClientRect();
            let newsRect = dom.getBoundingClientRect();
            if (lastDom.leftDistance > containerWidth - 20) return false;
            let lastS = lastDom.leftDistance;
            let lastV = lastDom.moveV;
            let lastT = lastS / lastV;
            let newsS = containerWidth;
            let newsV = (containerWidth + newsRect.width) / this.duration;
            let newsT = newsS / newsV;
            if (newsT < lastT) {
              return false;
            }
          }
          return true;
        }
        getCurrentDataLen() {
          return this.data.length;
        }
        stop() {
          window.cancelAnimationFrame(this.timer);
          this.timer = null;
          this.channel.map((val) => {
            val.style.transform = `translateX(-${val.currentPosition}px)`;
            val.stopStartTime = new Date().getTime();
            val.distance = val.width + containerWidth;
          });
        }
        start() {
          this.channel.map((val) => {
            val.style.transform = `translateX(-${val.distance}px)`;
            val.stopEndTime = new Date().getTime();
            val.stopTime += val.stopEndTime - val.stopStartTime;
            val.style.transition = `all ${val.leftDruation}s linear 0s`;
          });
          this.timer = window.requestAnimationFrame(
            this.loopWatcherLaunch.bind(this)
          );
        }
        addLaunch(value) {
          if (!value) {
            return;
          }
          if (this.data.length > 0) {
            this.data.push(value);
          } else {
            this.data.unshift(value);
            this.loopWatcherLaunch.call(
              this,
              initBullet(this.data[0], this.top)
            );
          }
        }
      }

      let barrageArr = [];
      let data = productData();
      // let barrage = new Barrage(container, ["222", "333"]);
      let barrage = new Barrage({ container, data, top: 0, duration: 5 });
      new Barrage({ container, data, top: 20, duration: 5 });
      new Barrage({ container, data, top: 40, duration: 5 });
      // barrageArr[i] = {
      //   instance: barrage,
      //   length: barrage.getCurrentDataLen(),
      // };
      stop_run.onclick = function () {
        barrage.stop();
      };
      start_run.onclick = function () {
        barrage.start();
      };
    </script>
  </body>
</html>
